package smallChanges.lineNumberMapping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import smallChanges.statements.ClassDeclaration;
import smallChanges.statements.FieldDeclarationStatement;
import smallChanges.statements.MethodDeclarationStatement;
import smallChanges.statements.SingleStatement;
import smallChanges.statements.Statement;
import smallChanges.statements.StatementComparator;
import smallChanges.statements.StatementMappingEntry;
import smallChanges.statements.StatementMappingResultSet;
import smallChanges.statements.StatementMappingEntry.StatementMappingStatus;
import smallChanges.util.SCLogger;

public class StatementMapper {

    protected SourceFile leftFile;
    protected SourceFile rightFile;
    protected boolean runTokenEditDistance;
    protected static final int JONKER_MULTIPLIER = 10000;
    protected double methodMappingThreshold = 2.0;
    protected double fieldMappingThreshold = 2.5;
    protected double classMappingThreshold = 0.9;
    protected double canfora = 0.4;
    protected static final boolean TURN_OFF_CLASS_RENAMING_CLASS_DECL_HEADER_ACCESS = true;
    protected boolean useMinTokenOrTextEditDistance;
    
    public StatementMapper() {
        runTokenEditDistance=false;
        useMinTokenOrTextEditDistance=false;
    }
    
    public StatementMapper(String leftFileStr, String srcVersionLeft,
            String rightFileStr, String srcVersionRight)
    {
        this();
        setSourceFiles(leftFileStr, srcVersionLeft,
                rightFileStr, srcVersionRight);
    }
    
    public void setSourceFiles(String leftFileStr, String srcVersionLeft,
            String rightFileStr, String srcVersionRight)
    {
        leftFile = new SourceFile(leftFileStr, srcVersionLeft);
        rightFile = new SourceFile(rightFileStr, srcVersionRight);
    }
    
    public StatementMappingResultSet run()
    throws IOException
    {
        StatementMappingResultSet rs=runStatementMapper();
        setValidMappings(rs);
        setProbablePossibleMappings(rs);
        return rs;
    }

    private void printStatements(ArrayList<Statement> methodStatements) {
        Iterator<Statement> iter = methodStatements.iterator();

        System.err.println("==================");
        while (iter.hasNext()) {
            System.err.println(iter.next().getImage());
        }
        System.err.println("==================");
    }

    private void mapImports(ArrayList<String> leftImports,
            ArrayList<Statement> leftImportsStatements, 
            ArrayList<String> rightImports,
            ArrayList<Statement> rightImportsStatements,
            StatementMappingResultSet resultSet) throws IOException {
    	
        // run diff on each set of imports
        org.incava.util.diff.Diff differ = new org.incava.util.diff.Diff(
                leftImportsStatements, rightImportsStatements,
                new StatementComparator());
        StatementMappingResultSet localResultSet = new StatementMappingResultSet();

        List diffResults = differ.diff();

        Iterator diffs = diffResults.iterator();
        int leftCurrLine = 0;
        int rightCurrLine = 0;

        while (diffs.hasNext()) {
            org.incava.util.diff.Difference diff = (org.incava.util.diff.Difference) diffs.next();

            for (; rightCurrLine < diff.getAddedStart()
                    && leftCurrLine < diff.getDeletedStart(); rightCurrLine++, leftCurrLine++) {
                localResultSet.add(new StatementMappingEntry(
                        leftImportsStatements.get(leftCurrLine),rightImportsStatements.get(rightCurrLine), 0,StatementMappingStatus.VALID));
            }

            // map the diff chunk!
            // i need to handle pure adds and pure deletes here!
            // no output for pure adds or pure delete!
            if (!(diff.getAddedEnd() == -1 || diff.getDeletedEnd() == -1)) {
                mapLines(leftImports, leftImportsStatements, diff.getDeletedStart(), diff.getDeletedEnd(),
                        rightImports, rightImportsStatements, diff.getAddedStart(), diff.getAddedEnd(),localResultSet);
            }
            if (diff.getDeletedEnd() == -1) {
                // pure add
                for (int rightLine = rightCurrLine; rightLine <= diff.getAddedEnd(); rightLine++) {
                    localResultSet.add(new StatementMappingEntry(SingleStatement.NullStatement,rightImportsStatements.get(rightLine), 0,StatementMappingStatus.VALID));

                }
            }
            if (diff.getAddedEnd() == -1) {
                // pure Delete
                for (int leftLine = leftCurrLine; leftLine <= diff.getDeletedEnd(); leftLine++) {
                    localResultSet.add(new StatementMappingEntry(leftImportsStatements.get(leftLine), SingleStatement.NullStatement, 0, StatementMappingStatus.VALID));

                }
            }
            if (diff.getAddedEnd() == -1) {
                rightCurrLine = diff.getAddedStart();
            } else {
                rightCurrLine = diff.getAddedEnd() + 1;

            }
            if (diff.getDeletedEnd() == -1) {
                leftCurrLine = diff.getDeletedStart();
            } else {
                leftCurrLine = diff.getDeletedEnd() + 1;
            }
        }

        for (int rightLine = rightCurrLine, leftLine = leftCurrLine; rightLine < rightImportsStatements.size()
                && leftLine < leftImportsStatements.size(); rightLine++, leftLine++) {
            localResultSet.add(new StatementMappingEntry(leftImportsStatements.get(leftLine), rightImportsStatements.get(rightLine), 
                    0, StatementMappingStatus.VALID));
        }

        calculateCrossings(localResultSet);

        resultSet.addAll(localResultSet);
    }
    
    public void mapMethod(String methodSig, ArrayList<String> leftMethodBody,
            ArrayList<Statement> leftMethodStatements, int leftMethodStartLine,
            ArrayList<String> rightMethodBody,
            ArrayList<Statement> rightMethodStatements,
            int rightMethodStartLine, StatementMappingResultSet resultSet,
            boolean renamedMethod) throws IOException {
        // run diff on each method
        org.incava.util.diff.Diff differ = new org.incava.util.diff.Diff(
                leftMethodStatements, rightMethodStatements,
                new StatementComparator());
        StatementMappingResultSet localResultSet = new StatementMappingResultSet();

        List diffResults = differ.diff();

        Iterator diffs = diffResults.iterator();
        int leftCurrLine = 0;
        int rightCurrLine = 0;

        while (diffs.hasNext()) {
            org.incava.util.diff.Difference diff = (org.incava.util.diff.Difference) diffs.next();

            // System.err.println(diff.toString());

            for (; rightCurrLine < diff.getAddedStart()
                    && leftCurrLine < diff.getDeletedStart(); rightCurrLine++, leftCurrLine++) {
                localResultSet.add(new StatementMappingEntry(
                        leftMethodStatements.get(leftCurrLine),rightMethodStatements.get(rightCurrLine), 0,StatementMappingStatus.VALID));
            }

            // map the diff chunk!
            // i need to handle pure adds and pure deletes here!
            // no output for pure adds or pure delete!
            if (!(diff.getAddedEnd() == -1 || diff.getDeletedEnd() == -1)) {
                mapLines(leftMethodBody, leftMethodStatements, diff.getDeletedStart(), diff.getDeletedEnd(),
                        rightMethodBody, rightMethodStatements, diff.getAddedStart(), diff.getAddedEnd(),localResultSet);
            }
            if (diff.getDeletedEnd() == -1) {
                // pure add
                for (int rightLine = rightCurrLine; rightLine <= diff
                        .getAddedEnd(); rightLine++) {
                    localResultSet.add(new StatementMappingEntry(SingleStatement.NullStatement,rightMethodStatements.get(rightLine), 0,StatementMappingStatus.VALID));

                }
            }
            if (diff.getAddedEnd() == -1) {
                // pure Delete
                for (int leftLine = leftCurrLine; leftLine <= diff
                        .getDeletedEnd(); leftLine++) {
                    localResultSet.add(new StatementMappingEntry(leftMethodStatements.get(leftLine), SingleStatement.NullStatement, 0, StatementMappingStatus.VALID));

                }
            }
            if (diff.getAddedEnd() == -1) {
                rightCurrLine = diff.getAddedStart();
            } else {
                rightCurrLine = diff.getAddedEnd() + 1;

            }
            if (diff.getDeletedEnd() == -1) {
                leftCurrLine = diff.getDeletedStart();
            } else {
                leftCurrLine = diff.getDeletedEnd() + 1;
            }
        }

        for (int rightLine = rightCurrLine, leftLine = leftCurrLine; rightLine < rightMethodStatements.size()
                && leftLine < leftMethodStatements.size(); rightLine++, leftLine++) {
            localResultSet.add(new StatementMappingEntry(leftMethodStatements.get(leftLine), rightMethodStatements.get(rightLine), 
                    0, StatementMappingStatus.VALID));
        }

        calculateCrossings(localResultSet);

        if (renamedMethod) {
            double val = (((double) localResultSet.size()) / (leftMethodStatements.size() + rightMethodStatements.size()));
            SCLogger.getLogger().trace("RENAMED METHOD VAL " + val);
            
            /* 
             * find how many lines mapped successfully vs how many could have 
             * mapped successfully.
             * 
             */
            int count=0;
            Iterator<StatementMappingEntry> iter = localResultSet.iterator();
            while(iter.hasNext())
            {
            	if(iter.next().getNormalizedEditDistance() <= canfora)
            	{
            		count++;
            	}
            }
            
//            double val2 = (((double) count) / Math.max(leftMethodStatements.size() , rightMethodStatements.size()));
            double val2 = (((double) count) / ( (leftMethodStatements.size() + rightMethodStatements.size() )/2 ));
            System.err.println("RENAMED METHOD VAL2 " + val2);
            
            resultSet.addAll(localResultSet);
        } else {
            resultSet.addAll(localResultSet);
        }
    }

    private void mapFields(ClassSource leftClass, ClassSource rightClass,
            StatementMappingResultSet resultSet, String[] className)
            throws IOException {
        Iterator<String> leftFieldIter = leftClass.getFieldNameIterator();
        ArrayList<Object> leftFieldNoExactMatch = new ArrayList<Object>();
        ArrayList<Object> rightFieldNoExactMatch = new ArrayList<Object>();
        StatementMappingResultSet localResultSet = new StatementMappingResultSet();

        while (leftFieldIter.hasNext()) {
            String leftFieldName = leftFieldIter.next();

            FieldDeclarationStatement leftField = leftClass.getFieldDecl(leftFieldName);
            FieldDeclarationStatement rightField = rightClass.getFieldDecl(leftFieldName);
            if (rightField != null) {
                StatementMappingEntry entry = new StatementMappingEntry(
                        leftField,rightField, 0, StatementMappingStatus.VALID);
                localResultSet.add(entry);
            } else {
                leftFieldNoExactMatch.add(leftField);
            }
        }

        // find unmatched fields on the right
        Iterator<String> rightFieldIterator = rightClass.getFieldNameIterator();
        while (rightFieldIterator.hasNext()) {
            String rightFieldName = rightFieldIterator.next();

            if (leftClass.getFieldDecl(rightFieldName) == null) {
                rightFieldNoExactMatch.add(rightClass.getFieldDecl(rightFieldName));

            }
        }

        findRenamings(leftClass, rightClass, leftFieldNoExactMatch,
                rightFieldNoExactMatch, fieldMappingThreshold, localResultSet);

        /* print deleted fields */
        Iterator<Object> leftNoExactMatchIter = leftFieldNoExactMatch
                .iterator();
        ArrayList<Statement> leftMissingStatements = new ArrayList<Statement>();
        while (leftNoExactMatchIter.hasNext()) {
            leftMissingStatements.add((Statement) leftNoExactMatchIter.next());
        }
        pureDelete(leftMissingStatements, localResultSet);

        /* print added fields */
        Iterator<Object> rightNoExactMatchIter = rightFieldNoExactMatch.iterator();
        ArrayList<Statement> rightAddedStatements = new ArrayList<Statement>();
        while (rightNoExactMatchIter.hasNext()) {
            rightAddedStatements.add((Statement) rightNoExactMatchIter.next());
        }
        pureAdd(rightAddedStatements, localResultSet);
        calculateCrossings(localResultSet);
        resultSet.addAll(localResultSet);
    }

    private void mapClassArtifacts(ClassSource leftClass,
            ClassSource rightClass, StatementMappingResultSet resultSet,
            String[] className) throws IOException {
        Statement leftHeader = leftClass.getClassDecl().getHeader();
        Statement rightHeader = rightClass.getClassDecl().getHeader();

        resultSet.add(new StatementMappingEntry(leftHeader,rightHeader,0, StatementMappingStatus.VALID));
    }

    private static ArrayList<Statement> convert(List<Statement> list)
    {
        ArrayList<Statement> result=new ArrayList<Statement>();
        for (Statement s : list) {
            result.add(s);
        }
        return result;
    }
    
//    private void mapMethods(FlatClassInfo leftClass, FlatClassInfo rightClass,
//            StatementMappingResultSet resultSet, String[] className)
//    throws IOException
//    {
//        ArrayList<Object> leftMethodNoExactMatch = new ArrayList<Object>();
//        ArrayList<Object> rightMethodNoExactMatch = new ArrayList<Object>();
//        
//        for (FlatMethodInfo leftMethod : leftClass.getFlatMethodInfoList()) {
//            String leftMethodSig=leftMethod.getSignature();
//            ArrayList<String> leftMethodBody=leftMethod.getMethodBodyAsStringList();
//            // try to find a method in the right-class with the same signature
//            // as the left-class
//            FlatMethodInfo rightMethod=rightClass.getMethod(leftMethodSig);
//            
//            if (rightMethod!=null) {
//                // found a match!
//                int leftMethodStartLine=-1;
//                //leftClass.getMethodStartLine(leftMethodSig)
//                int rightMethodStartLine=-1;
//                //rightClass.getMethodStartLine(leftMethodSig)
//                ArrayList<String> rightMethodBody=rightMethod.getMethodBodyAsStringList();
//                mapMethod(leftMethodSig, 
//                        leftMethodBody, convert(leftMethod.getStatements()),
//                        leftMethodStartLine,
//                        rightMethodBody, convert(rightMethod.getStatements()), 
//                        rightMethodStartLine,  
//                        resultSet,false);
//            } else {
//             // unmatched, save for later analysis
//                leftMethodNoExactMatch.add(leftMethod);
//            }
//            // TODO finish this method???
//            
//            /* find unmatched methods in right file */
////            for ()
////            Iterator<String> rightMethodSigIterator = rightClass
////                    .getMethodSignatureIterator();
////            while (rightMethodSigIterator.hasNext()) {
////                String rightMethodSig = rightMethodSigIterator.next();
////
////                if (leftClass.getMethodDeclarationStatement(rightMethodSig) == null) {
////                    rightMethodNoExactMatch.add(rightClass
////                            .getMethodDeclarationStatement(rightMethodSig));
////
////                }
////            }
//        }
//    }
        
    private void mapMethods(ClassSource leftClass, ClassSource rightClass,
            StatementMappingResultSet resultSet, String[] className)
            throws IOException {

        Iterator<String> leftMethodSigIterator = leftClass.getMethodSignatureIterator();

        ArrayList<Object> leftMethodNoExactMatch = new ArrayList<Object>();
        ArrayList<Object> rightMethodNoExactMatch = new ArrayList<Object>();

        while (leftMethodSigIterator.hasNext()) {
            String leftMethodSig = leftMethodSigIterator.next();
            ArrayList<Statement> leftMethodStatements = new ArrayList<Statement>();
            ArrayList<Statement> rightMethodStatements = new ArrayList<Statement>();

            ArrayList<String> leftMethodBody = leftClass.getCleanedOutMethodBody(leftMethodSig,
                            leftMethodStatements);
            ArrayList<String> rightMethodBody = rightClass.getCleanedOutMethodBody(leftMethodSig,
                            rightMethodStatements);

            if (rightMethodBody != null) {
                // found a match!
                mapMethod(leftMethodSig, leftMethodBody, leftMethodStatements,
                        leftClass.getMethodStartLine(leftMethodSig),rightMethodBody, 
                        rightMethodStatements, rightClass.getMethodStartLine(leftMethodSig), resultSet,
                        false);
            } else {
                // unmatched, save for later analysis
                leftMethodNoExactMatch.add(leftClass
                        .getMethodDeclarationStatement(leftMethodSig));
            }
        }

        /* find unmatched methods in right file */
        Iterator<String> rightMethodSigIterator = rightClass
                .getMethodSignatureIterator();
        while (rightMethodSigIterator.hasNext()) {
            String rightMethodSig = rightMethodSigIterator.next();

            if (leftClass.getMethodDeclarationStatement(rightMethodSig) == null) {
                rightMethodNoExactMatch.add(rightClass
                        .getMethodDeclarationStatement(rightMethodSig));

            }
        }

        findRenamings(leftClass, rightClass, leftMethodNoExactMatch,
                rightMethodNoExactMatch, methodMappingThreshold, resultSet);

        /* print deleted methods */
        Iterator<Object> leftNoExactMatchIter = leftMethodNoExactMatch
                .iterator();
        while (leftNoExactMatchIter.hasNext()) {
            String leftMethodSig = ((MethodDeclarationStatement) leftNoExactMatchIter
                    .next()).getSignature();

            ArrayList<Statement> leftMethodStatements = new ArrayList<Statement>();
            ArrayList<String> leftMethodBody = leftClass
                    .getCleanedOutMethodBody(leftMethodSig,
                            leftMethodStatements);

            pureDelete(leftMethodStatements, resultSet);
        }

        /* print added methods */
        Iterator<Object> rightNoExactMatchIter = rightMethodNoExactMatch
                .iterator();
        while (rightNoExactMatchIter.hasNext()) {
            String rightMethodSig = ((MethodDeclarationStatement) rightNoExactMatchIter
                    .next()).getSignature();

            ArrayList<Statement> rightMethodStatements = new ArrayList<Statement>();
            ArrayList<String> rightMethodBody = rightClass
                    .getCleanedOutMethodBody(rightMethodSig,
                            rightMethodStatements);

            pureAdd(rightMethodStatements, resultSet);
        }
    }

    private void findRenamings(ClassSource leftClass, ClassSource rightClass,
            ArrayList<Object> leftNoExactMatch,
            ArrayList<Object> rightNoExactMatch, double THRESHOLD,
            StatementMappingResultSet resultSet) {
        HashMap<Object, HashMap<Object, RenamedSourceObjectComparison>> renamedMethodComp = new HashMap<Object, HashMap<Object, RenamedSourceObjectComparison>>();
        /* try to match previously unmatched objects */
        Iterator<Object> leftNoExactMatchIter = leftNoExactMatch.iterator();
        while (leftNoExactMatchIter.hasNext()) {

            Object leftMethod = leftNoExactMatchIter.next();
            HashMap<Object, RenamedSourceObjectComparison> possibleMatches = new HashMap<Object, RenamedSourceObjectComparison>();

            Iterator<Object> rightNoExactMatchIter = rightNoExactMatch.iterator();
            while (rightNoExactMatchIter.hasNext()) {

                Object rightMethod = rightNoExactMatchIter.next();

                RenamedSourceObjectComparison comparison = RenamedSourceObjectComparisonFactory
                        .getRenamedSourceObjectComparison(leftMethod, rightMethod, leftClass, rightClass, this);

                possibleMatches.put(rightMethod, comparison);
            }
            renamedMethodComp.put(leftMethod, possibleMatches);
        }

        if (leftNoExactMatch.size() > 0 && rightNoExactMatch.size() > 0) {
            /* match likely refactoring candidates */
            int[][] weightedGraph;
            double[][] refactoringMapping;
            int maxSize = Math.max(leftNoExactMatch.size(), rightNoExactMatch
                    .size());

            weightedGraph = findRenamingWeightedBipartiteGraphForJonker(renamedMethodComp, leftNoExactMatch, 
            		rightNoExactMatch, maxSize);

            // refactoringMapping =
            // munkres.HungarianAlgorithm.RunHungarian(weightedGraph);
            int dim = maxSize /* * 2 */;
            int[] rowsol = new int[dim];
            int[] colsol = new int[dim];
            int[] u = new int[dim];
            int[] v = new int[dim];

            JonkerVolgenant.lap(dim, weightedGraph, rowsol, colsol, u, v);
            refactoringMapping = convertJonkerToHungarianResult(rowsol,
                    weightedGraph, maxSize);

            /* print renamed methods */
            /*
             * the lower the comparison stat the better 0-7
             */

            ArrayList<Object> renamedLeft = new ArrayList<Object>();
            ArrayList<Object> renamedRight = new ArrayList<Object>();
            for (int index = 0; index < maxSize; index++) {
                try {
                    if ((int) refactoringMapping[index][0] < leftNoExactMatch.size()
                            && (int) refactoringMapping[index][1] < rightNoExactMatch.size()) {

                        Object leftObj = (Object) leftNoExactMatch.get((int) refactoringMapping[index][0]);
                        Object rightObj = (Object) rightNoExactMatch.get((int) refactoringMapping[index][1]);

                        doRenaming(leftObj, rightObj, leftClass, rightClass,
                                refactoringMapping[index][2], THRESHOLD,resultSet);

                        if (refactoringMapping[index][2] <= THRESHOLD) {
                            printSimilarObject((int)refactoringMapping[index][0],(int)refactoringMapping[index][1],
                            maxSize, weightedGraph, leftNoExactMatch, rightNoExactMatch, leftObj, rightObj,
                            renamedMethodComp);

                            // mark for removal from left/right NoExactMatch
                            renamedLeft.add(leftNoExactMatch.get((int) refactoringMapping[index][0]));
                            renamedRight.add(rightNoExactMatch.get((int) refactoringMapping[index][1]));
                        }
                    } else if ((int) refactoringMapping[index][0] < leftNoExactMatch.size()) {
                        Object leftObj = (Object) leftNoExactMatch.get((int) refactoringMapping[index][0]);

                        noMatchLeft(leftObj, refactoringMapping[index][2]);
                    } else if ((int) refactoringMapping[index][1] < rightNoExactMatch.size()) {
                        Object rightObj = (Object) rightNoExactMatch.get((int) refactoringMapping[index][1]);

                        noMatchRight(rightObj, refactoringMapping[index][2]);

                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    System.err.println("ERROR: WEIGHTEDGRAPH");
                    /*
                     * for(int i=0;i<maxSize;i++) { for(int j=0;j<maxSize;j++) {
                     * System.err.print(weightedGraph[i][j]+"\t"); }
                     * System.err.println(""); } System.err.println("\n");
                     */
                }

            }
            // remove from left/right NoExactMatch
            Iterator<Object> deletionIterator = renamedLeft.iterator();
            while (deletionIterator.hasNext()) {
                leftNoExactMatch.remove(deletionIterator.next());
            }
            deletionIterator = renamedRight.iterator();
            while (deletionIterator.hasNext()) {
                rightNoExactMatch.remove(deletionIterator.next());
            }

        }
    }

    private void printSimilarObject(int v0Index, int v1Index, int maxSize,
            int[][] weightedGraph, ArrayList<Object> leftNoExactMatch,
            ArrayList<Object> rightNoExactMatch, Object left, Object right,
            HashMap<Object, HashMap<Object, RenamedSourceObjectComparison>> renamedMethodComp) {

        if (left.getClass() == right.getClass()) {

            if (left.getClass() == MethodDeclarationStatement.class) {
                HashMap<Integer, ArrayList<Integer>> row = new HashMap<Integer, ArrayList<Integer>>();
                HashMap<Integer, ArrayList<Integer>> col = new HashMap<Integer, ArrayList<Integer>>();

                // //////////
                for (int i = 0; i < maxSize; i++) {
                    Integer weight = new Integer(weightedGraph[i][v1Index]);
                    // System.err.println(weight.intValue());
                    if (i != v0Index) {
                        if (row.containsKey(weight)) {
                            row.get(weight).add(new Integer(i));
                        } else {
                            row.put(weight, new ArrayList<Integer>());
                            row.get(weight).add(new Integer(i));
                        }
                    }
                }
                Set<Integer> set;
                set = row.keySet();
                Integer[] rowWeights = set.toArray(new Integer[0]);

                java.util.Arrays.sort(rowWeights);

                int count = 0;
                for (int index = 0; index < rowWeights.length && count < 3; index++) {
                    ArrayList<Integer> indexes = row.get(rowWeights[index]);

                    Iterator<Integer> iter = indexes.iterator();
                    while (iter.hasNext() && count < 3) {
                        Integer rowIndex = iter.next();
                        if (rowIndex.intValue() < leftNoExactMatch.size()) {

                            MethodDeclarationStatement stmt = (MethodDeclarationStatement) leftNoExactMatch
                                    .get(rowIndex.intValue());
                            System.err
                                    .println("SIMILAR: "
                                            + stmt.getSignature()
                                            + " AND v1 STAT: "
                                            + weightedGraph[rowIndex.intValue()][v1Index]
                                            / ((double) JONKER_MULTIPLIER));
                            System.err.println("VAL2 "+((RenamedMethodComparison) renamedMethodComp.get(stmt).get(right)).getBodySimilarity());
                            count++;
                        } else {
                            count = 3;
                        }
                    }
                }

                // /////////

                for (int i = 0; i < maxSize; i++) {
                    Integer weight = new Integer(weightedGraph[v0Index][i]);
                    // System.err.println(weight.intValue());
                    if (i != v1Index) {
                        if (col.containsKey(weight)) {
                            col.get(weight).add(new Integer(i));
                        } else {
                            col.put(weight, new ArrayList<Integer>());
                            col.get(weight).add(new Integer(i));
                        }
                    }
                }
                set = col.keySet();
                rowWeights = set.toArray(new Integer[0]);

                java.util.Arrays.sort(rowWeights);

                count = 0;
                for (int index = 0; index < rowWeights.length && count < 3; index++) {
                    ArrayList<Integer> indexes = col.get(rowWeights[index]);

                    Iterator<Integer> iter = indexes.iterator();
                    while (iter.hasNext() && count < 3) {
                        Integer rowIndex = iter.next();
                        if (rowIndex.intValue() < rightNoExactMatch.size()) {
                            MethodDeclarationStatement stmt = (MethodDeclarationStatement) rightNoExactMatch
                                    .get(rowIndex.intValue());
                            System.err.println("SIMILAR: v0 AND "
                                    + stmt.getSignature()
                                    + " STAT: "
                                    + weightedGraph[v0Index][rowIndex
                                            .intValue()]
                                    / ((double) JONKER_MULTIPLIER));
                            System.err.println("VAL2 "+((RenamedMethodComparison) renamedMethodComp.get(left).get(stmt)).getBodySimilarity());

                            count++;
                        } else {
                            count = 3;// we have run off the end of the right
                                        // functions
                        }
                    }
                }

            }
        }

    }

    private void mapStaticInitializers(ClassSource leftClass,
            ClassSource rightClass, StatementMappingResultSet resultSet,
            String[] className) throws IOException {
        ArrayList<Statement> leftStatements = new ArrayList<Statement>();
        ArrayList<Statement> rightStatements = new ArrayList<Statement>();
        StatementMappingResultSet localResultSet = new StatementMappingResultSet();

        ArrayList<String> leftBody = leftClass.getStaticInitializerStatements(leftStatements);
        ArrayList<String> rightBody = rightClass.getStaticInitializerStatements(rightStatements);

        if (rightBody != null && leftBody != null) {
            // found a match!
            mapMethod("", leftBody, leftStatements, leftClass.getStaticInitializerStartLine(), rightBody,
                    rightStatements, rightClass.getStaticInitializerStartLine(), localResultSet, false);

        } else if (rightBody != null) {
            pureAdd(rightStatements, localResultSet);
        } else if (leftBody != null) {
            pureDelete(rightStatements, localResultSet);
        }
        //calculateCrossings(localResultSet); // mapMethod does this for us
        resultSet.addAll(localResultSet);
    }

    private void mapClass(ClassSource leftClass, ClassSource rightClass,
            StatementMappingResultSet resultSet, String[] className)
            throws IOException {
    	
    	//map imports?
    	
        /*
         * what we do not do: try to find methods/fields that move between
         * classes. or inner classes that move between outer classes
         */

        if( !TURN_OFF_CLASS_RENAMING_CLASS_DECL_HEADER_ACCESS ) // ?
        {
        	mapClassArtifacts(leftClass, rightClass, resultSet, className);
        }
        
        // find matching methods
        mapMethods(leftClass, rightClass, resultSet, className);

        // find matching fields
        mapFields(leftClass, rightClass, resultSet, className);

        // find matching static initializers
        mapStaticInitializers(leftClass, rightClass, resultSet, className);

        // find matching inner classes
        mapInnerClasses(leftClass, rightClass, resultSet, className);
    }

    private void mapInnerClasses(ClassSource leftClass, ClassSource rightClass,
            StatementMappingResultSet resultSet, String[] className)
            throws IOException {
        Iterator<String> leftIter = leftClass.getInnerClassNameIterator();
        ArrayList<Object> leftNoExactMatch = new ArrayList<Object>();
        ArrayList<Object> rightNoExactMatch = new ArrayList<Object>();

        while (leftIter.hasNext()) {
            String[] leftInnerClassName = new String[1];
            leftInnerClassName[0] = leftIter.next();
            ClassSource leftInnerClass = leftClass
                    .getInnerClass(leftInnerClassName[0]);

            ClassSource rightInnerClass = rightClass
                    .getInnerClass(leftInnerClassName[0]);
            if (rightInnerClass != null) {
                mapClass(leftInnerClass, rightInnerClass, resultSet,
                        leftInnerClassName);
            } else {
                leftNoExactMatch.add(leftInnerClass);
            }
        }

        Iterator<String> rightIter = rightClass.getInnerClassNameIterator();
        while (rightIter.hasNext()) {
            String[] rightInnerClassName = new String[1];
            rightInnerClassName[0] = rightIter.next();
            if (leftClass.getInnerClass(rightInnerClassName[0]) == null) {
                rightNoExactMatch.add(rightClass
                        .getInnerClass(rightInnerClassName[0]));
            }
        }
        mapRenamedClasses(leftClass, rightClass, leftNoExactMatch,
                rightNoExactMatch, resultSet);
    }

    private void mapRenamedClasses(ClassSource leftClass,
            ClassSource rightClass, ArrayList<Object> leftNoExactMatch,
            ArrayList<Object> rightNoExactMatch,
            StatementMappingResultSet resultSet) throws IOException {

    	// THIS TURNS OFF mapRenamedClasses!
    	if (TURN_OFF_CLASS_RENAMING_CLASS_DECL_HEADER_ACCESS )
        {
    		return;
        }
    	
        findRenamings(leftClass, rightClass, leftNoExactMatch,
                rightNoExactMatch, classMappingThreshold, resultSet);

        /* print deleted inner classes */
        Iterator<Object> leftNoExactMatchIter = leftNoExactMatch.iterator();
        while (leftNoExactMatchIter.hasNext()) {

            pureDelete((ClassSource) leftNoExactMatchIter.next(), resultSet);
        }

        /* print added inner classes */
        Iterator<Object> rightNoExactMatchIter = rightNoExactMatch.iterator();
        while (rightNoExactMatchIter.hasNext()) {
            pureAdd((ClassSource) rightNoExactMatchIter.next(), resultSet);
        }
    }

    private void pureAdd(ClassSource classSrc,
            StatementMappingResultSet resultSet) {
        ClassDeclaration classDecl = classSrc.getClassDecl();
        ArrayList<Statement> addedStatements = new ArrayList<Statement>();

        // fields
        List<FieldDeclarationStatement> fieldList = classDecl.getFieldList();
        Iterator<FieldDeclarationStatement> fieldIter = fieldList.iterator();
        while (fieldIter.hasNext()) {
            FieldDeclarationStatement fieldStmt = fieldIter.next();
            addedStatements.add(fieldStmt);
        }

        // methods
        List<MethodDeclarationStatement> methodList = classDecl.getMethodList();
        Iterator<MethodDeclarationStatement> methodIter = methodList.iterator();
        while (methodIter.hasNext()) {
            MethodDeclarationStatement methodStmt = methodIter.next();

            String methodSig = methodStmt.getSignature();

            ArrayList<Statement> methodStatements = new ArrayList<Statement>();
            ArrayList<String> methodBody = classSrc.getCleanedOutMethodBody(
                    methodSig, methodStatements);

            addedStatements.addAll(methodStatements);
        }

        // static initializer
        if (classSrc.getStaticInitializer() != null) {
            ArrayList<Statement> staticInitStatements = new ArrayList<Statement>();

            ArrayList<String> staticInitBody = classSrc
                    .getStaticInitializerStatements(staticInitStatements);

            addedStatements.addAll(staticInitStatements);
        }

        pureAdd(addedStatements, resultSet);

        Iterator<String> innerClassIter = classSrc.getInnerClassNameIterator();
        while (innerClassIter.hasNext()) {
            ClassSource innerClassSource = classSrc
                    .getInnerClass(innerClassIter.next());
            pureAdd(innerClassSource, resultSet);
        }
        if( !TURN_OFF_CLASS_RENAMING_CLASS_DECL_HEADER_ACCESS )
        {
        	resultSet.add(new StatementMappingEntry(SingleStatement.NullStatement, classSrc.getClassDecl().getHeader(),  0,
        			StatementMappingStatus.VALID));
        }
    }

    private void pureDelete(ClassSource classSrc,
            StatementMappingResultSet resultSet) {
        ClassDeclaration classDecl = classSrc.getClassDecl();
        ArrayList<Statement> deletedStatements = new ArrayList<Statement>();

        // fields
        List<FieldDeclarationStatement> fieldList = classDecl.getFieldList();
        Iterator<FieldDeclarationStatement> fieldIter = fieldList.iterator();
        while (fieldIter.hasNext()) {
            FieldDeclarationStatement fieldStmt = fieldIter.next();
            deletedStatements.add(fieldStmt);
        }

        // methods
        List<MethodDeclarationStatement> methodList = classDecl.getMethodList();
        Iterator<MethodDeclarationStatement> methodIter = methodList.iterator();
        while (methodIter.hasNext()) {
            MethodDeclarationStatement methodStmt = methodIter.next();

            String methodSig = methodStmt.getSignature();

            ArrayList<Statement> methodStatements = new ArrayList<Statement>();
            ArrayList<String> methodBody = classSrc.getCleanedOutMethodBody(
                    methodSig, methodStatements);

            deletedStatements.addAll(methodStatements);
        }

        // static initializer
        if (classSrc.getStaticInitializer() != null) {
            ArrayList<Statement> staticInitStatements = new ArrayList<Statement>();

            ArrayList<String> staticInitBody = classSrc
                    .getStaticInitializerStatements(staticInitStatements);

            deletedStatements.addAll(staticInitStatements);
        }

        pureDelete(deletedStatements, resultSet);

        Iterator<String> innerClassIter = classSrc.getInnerClassNameIterator();
        while (innerClassIter.hasNext()) {
            ClassSource innerClassSource = classSrc
                    .getInnerClass(innerClassIter.next());
            pureDelete(innerClassSource, resultSet);
        }
        if( !TURN_OFF_CLASS_RENAMING_CLASS_DECL_HEADER_ACCESS )
        {

        	resultSet.add(new StatementMappingEntry(classSrc.getClassDecl().getHeader(), SingleStatement.NullStatement,  0,
        			StatementMappingStatus.VALID));
        }
    }

    private ArrayList<String> getStatementsAsStrings(ArrayList<Statement> stmts)
    {
    	ArrayList<String> strings = new ArrayList<String>();
    	
    	for(Statement s : stmts)
    	{
    		strings.add(s.toString());
    	}
    	return strings;
    }
    
    public StatementMappingResultSet runStatementMapper() throws IOException
	{
		
		/* this method produces a mapping of all statements in the left and
		 * right file.  Statements that only appear in the left or the right
		 * are mapped against the null statement.
		 * 
		 * None of these mappings are filtered by nED or nTED, that is left
		 * to the end user to do.
		 */
		StatementMappingResultSet resultSet = new StatementMappingResultSet(); // keep java happy
    
	    // map imports between the two SourceFiles
        ArrayList<Statement> leftImportsStatements = new ArrayList<Statement>();
        ArrayList<Statement> rightImportsStatements = new ArrayList<Statement>();

        leftImportsStatements.addAll(leftFile.getImportList());
        rightImportsStatements.addAll(rightFile.getImportList());
        
        ArrayList<String> leftImportsStrings = getStatementsAsStrings(leftImportsStatements);
        ArrayList<String> rightImportsStrings = getStatementsAsStrings(rightImportsStatements);

        mapImports( leftImportsStrings, leftImportsStatements,rightImportsStrings, 
                    rightImportsStatements, resultSet);
	    
		// run this per top level class		
		Iterator<String> classNameIter = leftFile.getClassNameIterator();
		// find matching classes
		
		ArrayList<Object> leftNoExactMatch = new ArrayList<Object>();

        ArrayList<Object> rightNoExactMatch = new ArrayList<Object>();

        while (classNameIter.hasNext()) {
            String leftClassName = classNameIter.next();
            ClassSource leftClass = leftFile.getClass(leftClassName);
            ClassSource rightClass = rightFile.getClass(leftClassName);
            String[] className = new String[1];

            className[0] = leftClassName;

            if (leftClass != null && rightClass != null) {
                mapClass(leftClass, rightClass, resultSet, className);
            } else {

                leftNoExactMatch.add(leftClass);
            }
        }
        classNameIter = rightFile.getClassNameIterator();
        while (classNameIter.hasNext()) {
            String className = classNameIter.next();
            if (leftFile.getClass(className) == null) {
                rightNoExactMatch.add(rightFile.getClass(className));
            }
        }
        mapRenamedClasses(null, null, leftNoExactMatch, rightNoExactMatch,
                resultSet);

        return resultSet;
    }

    // this is only public to allow the test cases to see it
    public static double[][] convertJonkerToHungarianResult(int[] rowsol,
            int[][] weightedGraph, int maxSize) {
        double retVal[][] = new double[maxSize][4]; // LineOne LineTwo Weight
                                                    // Crossings

        for (int i = 0; i < maxSize; i++) {
            retVal[i][0] = i;
            retVal[i][1] = rowsol[i] /*- maxSize ccw 9 july 2008*/;
            // System.err.println(i);
            retVal[i][2] = (double) Integer.MAX_VALUE;
            retVal[i][2] = weightedGraph[i][rowsol[i/* +maxSize */]]
                    / ((double) JONKER_MULTIPLIER);
            retVal[i][3] = 0;
        }
        return retVal;
    }

    private static <T> int[][] findRenamingWeightedBipartiteGraphForJonker(
            HashMap<T, HashMap<T, RenamedSourceObjectComparison>> possibleMatches,
            ArrayList<T> leftMethodNoExactMatch,
            ArrayList<T> rightMethodNoExactMatch, int size) {
        int[][] weightedGraph = new int[size/* +size */][size /* +size */];
        // fill top right
        //
        // x y 0 1 2 3 4
        // 0
        // 1
        // 2
        // 3
        // x = left (fileOne)
        // y = right (fileTwo)

        int fileOneIndex, fileTwoIndex;

        for (int i = 0; i < size /* +size */; i++) {
            for (int j = 0; j < size/* +size */; j++) {
                weightedGraph[i][j] = smallChanges.lineNumberMapping.JonkerVolgenant.BIG;
            }
        }

        /*
         * for( int i= 0; i< size; i++) { for(int j = size;j<size*2;j++) {
         * weightedGraph[i][j] = JonkerVolgenant.BIG-1; } }
         */

        for (fileOneIndex = 0; fileOneIndex < leftMethodNoExactMatch.size(); fileOneIndex++) {
            HashMap<T, RenamedSourceObjectComparison> matches = possibleMatches
                    .get(leftMethodNoExactMatch.get(fileOneIndex));

            for (fileTwoIndex = 0; fileTwoIndex < rightMethodNoExactMatch.size(); fileTwoIndex++) {
                RenamedSourceObjectComparison comparison = matches.get(rightMethodNoExactMatch.get(fileTwoIndex));
                /* the JV algorithm wants to minimize the weights of the graph */
                weightedGraph[fileOneIndex][fileTwoIndex ] = (int) ((comparison.getComparisonStatisticMax() - 
                		comparison.getComparisonStatistic()) * JONKER_MULTIPLIER);
            }
        }
        return weightedGraph;
    }

    private void pureDelete(ArrayList<Statement> leftMethodStatements,
            StatementMappingResultSet resultSet) {
        Iterator<Statement> statementIter = leftMethodStatements.iterator();
        while (statementIter.hasNext()) {
            resultSet.add(new StatementMappingEntry(statementIter.next(), SingleStatement.NullStatement, 0, StatementMappingStatus.VALID));
        }
    }

    private void pureAdd(ArrayList<Statement> leftMethodStatements,
            StatementMappingResultSet resultSet) {
        Iterator<Statement> statementIter = leftMethodStatements.iterator();
        while (statementIter.hasNext()) {
            resultSet.add(new StatementMappingEntry(SingleStatement.NullStatement,statementIter.next(),0,StatementMappingStatus.VALID));
        }
    }

    public void printLeftFile() {
        System.err.println(leftFile.toString());
    }

    public void printRightFile() {
        System.err.println(rightFile.toString());
    }

    private ArrayList<String> fillWithStringsFromStatements(
            ArrayList<Statement> methodStatements, int start, int end) {
        ArrayList<String> methodBody = new ArrayList<String>();

        Iterator<Statement> stateIter = methodStatements.iterator();
        int i = 0;
        while (stateIter.hasNext()) {
            if (i >= start && i <= end) {
                methodBody.add(stateIter.next().getImage());
            } else {
                stateIter.next();
            }
            i++;
        }

        return methodBody;
    }

    private ArrayList<Statement> fillWithStatements(
            ArrayList<Statement> methodStatements, int start, int end) {
        ArrayList<Statement> methodBody = new ArrayList<Statement>();

        Iterator<Statement> stateIter = methodStatements.iterator();
        int i = 0;
        while (stateIter.hasNext()) {
            if (i >= start && i <= end) {
                methodBody.add(stateIter.next());
            } else {
                stateIter.next();
            }
            i++;
        }
        return methodBody;
    }

    private void mapLines(ArrayList<String> leftLinesOrig, ArrayList<Statement> leftMethodStatements, int startLineLeft,
            int endLineLeft, ArrayList<String> rightLinesOrig, ArrayList<Statement> rightMethodStatements, int startLineRight,
            int endLineRight, StatementMappingResultSet resultSet)
            throws IOException {
        double[] weightedGraph;
        double[][] lineNumberMapping;
        double tmp;
        int newEndLineLeft;
        int newEndLineRight;
        ArrayList<String> leftLines;
        ArrayList<String> rightLines;
        ArrayList<Statement> leftLinesStatements;
        ArrayList<Statement> rightLinesStatements;
        int extraLines = 0;

        leftLines = fillWithStringsFromStatements(leftMethodStatements,startLineLeft, endLineLeft);
        rightLines = fillWithStringsFromStatements(rightMethodStatements,startLineRight, endLineRight);

        leftLinesStatements = fillWithStatements(leftMethodStatements,startLineLeft, endLineLeft);
        rightLinesStatements = fillWithStatements(rightMethodStatements,startLineRight, endLineRight);

        // when things get padded out i need to ignore the mapping of the pad
        // lines:

        /*
         * 1: value--; 2: otherValue--; -- 1: value++;
         * 
         * the two 1 lines should match. the lower chunk is padded out with a
         * line to match the size, but whatever matches to it should be ignored
         * 
         */
        if (endLineLeft - startLineLeft < endLineRight - startLineRight) {
            for (int i = (endLineRight - startLineRight)- (endLineLeft - startLineLeft); i > 0; i--) {
                leftLines.add("");
                leftLinesStatements.add(null);
                extraLines--;
            }
        } else if (endLineLeft - startLineLeft > endLineRight - startLineRight) {
            for (int i = (endLineLeft - startLineLeft)- (endLineRight - startLineRight); i > 0; i--) {
                rightLines.add("");
                rightLinesStatements.add(null);
                extraLines++;
            }
        }

        newEndLineLeft = leftLines.size() - 1;
        newEndLineRight = rightLines.size() - 1;

        weightedGraph = findEditDistWeightedBipartiteGraph(0 /* startLineLeft */, newEndLineLeft, 0 /* startLineRight */,
                newEndLineRight, leftLinesStatements, rightLinesStatements, runTokenEditDistance);

        // run hungarian algorithm (bipartite matching)
        lineNumberMapping = munkres.HungarianAlgorithm.RunHungarian(weightedGraph);

        // if rows > columns the HungarianAlgorithm transposes the
        // matrix!
        int flipMatrix = 0;
        /* never flip the matrix b/c we pad it out! */
        if (false && (newEndLineLeft - startLineLeft + 1) > (newEndLineRight- startLineRight + 1)) {
            // "OH NO FLIPPING THE MATRIX\n";
            flipMatrix = 1;
        }

        int index = 0;
        while (index < lineNumberMapping.length) {
            // remove blank lines and { }
            if (flipMatrix == 1) {
                tmp = lineNumberMapping[index][0];
                lineNumberMapping[index][0] = lineNumberMapping[index][1];
                lineNumberMapping[index][1] = tmp;
                // }
            }
            index++;
        }

        // calculate crossing
        // don't do this here, we do this later on at the method level
        // this would only calculate crossings for this small diff chunk
        // which may not be accurate if the chunks move around.
        // lineNumberMapping = LineNumberMapping.calculateCrossings(lineNumberMapping);

        addToResultSet(lineNumberMapping, startLineLeft, startLineRight,
                endLineLeft, endLineRight, leftMethodStatements,
                rightMethodStatements /* , new FileWriter(System.err.) */,
                extraLines, resultSet);

        // output line number mapping
        // try {
        // outputToFile(lineNumberMapping, startLineLeft, startLineRight,
        // endLineLeft, endLineRight, leftMethodStatements,
        // rightMethodStatements /*, new FileWriter(System.err.)*/, extraLines);
        // } catch (IOException e) {
        // // TODO Auto-generated catch block
        // e.printStackTrace();
        // }
    }

    private double[] findEditDistWeightedBipartiteGraph(int startLineOne,
            int stopLineOne, int startLineTwo, int stopLineTwo,
            ArrayList<Statement> fileOneData, ArrayList<Statement> fileTwoData, boolean runTokenEditDistance) {

        double[][] weightedGraph;
        int fileOneSize = stopLineOne - startLineOne + 1;
        int fileTwoSize = stopLineTwo - startLineTwo + 1;
        double[] results = new double[2 + fileOneSize * fileTwoSize];

        int fileOneIndex, fileTwoIndex;
        Statement lineFromOne;
        Statement lineFromTwo;
        double normTokenEditDist;
        int i;

        weightedGraph = new double[(java.lang.Math
                .max(fileOneSize, fileTwoSize))][(java.lang.Math.max(
                fileOneSize, fileTwoSize))];

        results[0] = fileOneSize;
        results[1] = fileTwoSize;
        i = 2;
        /*
         * startLineOne --; stopLineOne --; startLineTwo --; stopLineTwo --;
         */

        for (fileOneIndex = startLineOne; fileOneIndex <= stopLineOne; fileOneIndex++) {

            lineFromOne = fileOneData.get(fileOneIndex);

            for (fileTwoIndex = startLineTwo; fileTwoIndex <= stopLineTwo; fileTwoIndex++) {

                lineFromTwo = fileTwoData.get(fileTwoIndex);

                if (lineFromOne == null || lineFromTwo == null) {
                    normTokenEditDist = 1.0;
                } else {
                    // System.err.println(lineFromOne.getImage() + " <> " +
                    // lineFromTwo.getImage());
                    if (runTokenEditDistance) {
                        normTokenEditDist = lineFromOne.getNormalizedLevenshteinTokenEditDistance(lineFromTwo);
                    } else {
                        normTokenEditDist = lineFromOne.getNormalizedLevenshteinEditDistance(lineFromTwo);
                    }
                    if( useMinTokenOrTextEditDistance )
                    {
                    	normTokenEditDist = java.lang.Math.min(
                    			lineFromOne.getNormalizedLevenshteinTokenEditDistance(lineFromTwo), 
                    			lineFromOne.getNormalizedLevenshteinEditDistance(lineFromTwo));
                    }
                }
                // System.err.println("-->"+lineFromOne + " || "+ lineFromTwo +
                // " >>> "+minEditDist);
                if (normTokenEditDist == 0.0) {
                    weightedGraph[fileOneIndex - startLineOne][fileTwoIndex- startLineTwo] = 0.0;
                    weightedGraph[fileTwoIndex - startLineTwo][fileOneIndex- startLineOne] = 0.0;
                } else {
                    /* really use use MAX(lineFromOneLength, lineFromTwoLength) */
                    weightedGraph[fileOneIndex - startLineOne][fileTwoIndex
                            - startLineTwo] = normTokenEditDist;
                    // (1.0 * minEditDist / java.lang.Math.max(lineFromOneLength
                    // , lineFromTwoLength));
                    weightedGraph[fileTwoIndex - startLineTwo][fileOneIndex
                            - startLineOne] = normTokenEditDist;
                    // (1.0 * minEditDist / java.lang.Math.max(lineFromOneLength
                    // , lineFromTwoLength));
                }
                results[i++] = weightedGraph[fileOneIndex - startLineOne][fileTwoIndex
                        - startLineTwo];
            }

        }
        return results;
    }

    private void addToResultSet(double[][] lineNumberMapping, int startLineOne,
            int startLineTwo, int endLineOne, int endLineTwo,
            ArrayList<Statement> fileOneData, ArrayList<Statement> fileTwoData,
            int extraLines, StatementMappingResultSet resultSet)
            throws IOException {

        // if extra lines < 0, then lines were added to LEFT
        // if extra lines > 0, then lines were added to RIGHT

        for (int index = 0; index < lineNumberMapping.length; index++) {
            if ((extraLines < 0 && (int) (lineNumberMapping[index][0] + startLineOne) > endLineOne /*
                                                                                                     * +
                                                                                                     * extraLines
                                                                                                     */)
                    || (extraLines > 0 && (int) (lineNumberMapping[index][1] + startLineTwo) > endLineTwo /*- extraLines*/)) {
                if (extraLines > 0) {
                    resultSet.add(new StatementMappingEntry(fileOneData.get((int) (lineNumberMapping[index][0])
                                    + startLineOne), SingleStatement.NullStatement, (int) lineNumberMapping[index][3],StatementMappingStatus.VALID));
                } else if (extraLines < 0) {
                    resultSet.add(new StatementMappingEntry(SingleStatement.NullStatement,
                    		fileTwoData.get((int) (lineNumberMapping[index][1])+ startLineTwo),
                            (int)lineNumberMapping[index][3], StatementMappingStatus.VALID));
                }
            } else {
                resultSet.add(new StatementMappingEntry(fileOneData.get((int) (lineNumberMapping[index][0])+ startLineOne), 
                		fileTwoData.get((int) (lineNumberMapping[index][1])+ startLineTwo),(int) lineNumberMapping[index][3],
                        StatementMappingStatus.INVALID));
            }
        }
    }

    public boolean isRunTokenEditDistance() {
        return runTokenEditDistance;
    }

    public void setRunTokenEditDistance(boolean runTokenEditDistance) {
        this.runTokenEditDistance = runTokenEditDistance;
    }

    private void doRenaming(Object left, Object right, ClassSource leftClass,
            ClassSource rightClass, double value, double THRESHOLD,
            StatementMappingResultSet resultSet) throws IOException {
    	
        if (left.getClass() == right.getClass()) {

            if (left.getClass() == MethodDeclarationStatement.class) {
                MethodDeclarationStatement leftStmt = (MethodDeclarationStatement) left;
                MethodDeclarationStatement rightStmt = (MethodDeclarationStatement) right;

                ArrayList<Statement> leftMethodStatements = new ArrayList<Statement>();
                ArrayList<Statement> rightMethodStatements = new ArrayList<Statement>();

                ArrayList<String> leftMethodBody = leftClass.getCleanedOutMethodBody(leftStmt.getSignature(),
                                leftMethodStatements);
                ArrayList<String> rightMethodBody = rightClass.getCleanedOutMethodBody(rightStmt.getSignature(),
                                rightMethodStatements);

                if (value <= THRESHOLD) {
                    // assume this is a renaming
                    // MAP!
                    // found a match!
                    System.err.println("\n\nRENAMED METHOD: "
                            + leftStmt.getSignature() + " AND "
                            + rightStmt.getSignature() + " STAT: " + value);
                    mapMethod(leftStmt.getSignature(), leftMethodBody,leftMethodStatements,
                            leftClass.getMethodStartLine(leftStmt.getSignature()), rightMethodBody,
                            rightMethodStatements, rightClass.getMethodStartLine(rightStmt.getSignature()), resultSet, true);

                } else {
                    System.err.println("\n\nNOT RENAMED METHOD: "
                            + leftStmt.getSignature() + " AND "
                            + rightStmt.getSignature() + " STAT: " + value);
                }

            } else if (left.getClass() == ClassSource.class) {

                ClassSource leftInnerClass, rightInnerClass;
                leftInnerClass = (ClassSource) left;
                rightInnerClass = (ClassSource) right;

                if (value <= THRESHOLD) {
                    // assume this is a renaming
                    // MAP!
                    // found a match!
                    System.err.println("\n\nRENAMED CLASS: "
                                    + leftInnerClass.getClassName() + " AND "
                                    + leftInnerClass.getClassName() + " STAT: "
                                    + value);
                    String[] leftClassName = new String[1];
                    leftClassName[0] = leftInnerClass.getClassName();
                    mapClass(leftInnerClass, rightInnerClass, resultSet,leftClassName);

                } else {
                    System.err.println("\n\nNOT RENAMED CLASS: "
                            + leftInnerClass.getClassName() + " AND "
                            + rightInnerClass.getClassName() + " STAT: "
                            + value);
                }
            } else if (left.getClass() == FieldDeclarationStatement.class) {
                FieldDeclarationStatement leftStmt = (FieldDeclarationStatement) left;
                FieldDeclarationStatement rightStmt = (FieldDeclarationStatement) right;

                if (value <= THRESHOLD) {
                    // assume this is a renaming
                    // MAP!
                    // found a match!
                    System.err
                            .println("\n\nRENAMED FIELD: " + leftStmt.getName()
                                    + " AND " + rightStmt.getName() + " STAT: "
                                    + value);
                    StatementMappingEntry entry = new StatementMappingEntry(leftStmt,rightStmt, 0, StatementMappingStatus.VALID);
                    resultSet.add(entry);
                } else {

                    System.err.println("\n\nNOT RENAMED FIELD: "
                            + leftStmt.getName() + " AND "
                            + rightStmt.getName() + " STAT: " + value);
                }
            }
        }
    }

    private void noMatchLeft(Object left, double value) {

        if (left.getClass() == MethodDeclarationStatement.class) {
            MethodDeclarationStatement leftStmt = (MethodDeclarationStatement) left;

            System.err.println("\n\nNOT RENAMED METHOD: "
                    + leftStmt.getSignature() + " AND <NULL> STAT: " + value);
        } else if (left.getClass() == ClassSource.class) {
            String leftName = "<NONE>", rightName = "<NONE>";
            leftName = ((ClassSource) left).getClassDecl().getName();

            System.err.println("\n\nNOT RENAMED CLASS: " + leftName + " AND "
                    + rightName + " STAT: " + value);
        } else if (left.getClass() == FieldDeclarationStatement.class) {
            String leftName = "<NONE>", rightName = "<NONE>";
            leftName = ((FieldDeclarationStatement) left).getName();

            System.err.println("\n\nNOT RENAMED FIELD: " + leftName + " AND "
                    + rightName + " STAT: " + value);
        }
    }

    private void noMatchRight(Object right, double value) {

        if (right.getClass() == MethodDeclarationStatement.class) {
            MethodDeclarationStatement rightStmt = (MethodDeclarationStatement) right;

            System.err.println("\n\nNOT RENAMED METHOD: <NULL> AND "
                    + rightStmt.getSignature() + " STAT: " + value);
        } else if (right.getClass() == ClassSource.class) {
            String leftName = "<NONE>", rightName = "<NONE>";
            rightName = ((ClassSource) right).getClassDecl().getName();

            System.err.println("\n\nNOT RENAMED CLASS: " + leftName + " AND "
                    + rightName + " STAT: " + value);

        } else if (right.getClass() == FieldDeclarationStatement.class) {
            String leftName = "<NONE>", rightName = "<NONE>";
            rightName = ((FieldDeclarationStatement) right).getName();

            System.err.println("\n\nNOT RENAMED FIELD: " + leftName + " AND "
                    + rightName + " STAT: " + value);
        }
    }

    private void calculateCrossings(StatementMappingResultSet resultSet) {
        Iterator<StatementMappingEntry> iter = resultSet.iterator();
        ArrayList<StatementMappingEntry> array = new ArrayList<StatementMappingEntry>(
                resultSet.size());
        while (iter.hasNext()) {
            StatementMappingEntry entry = iter.next();

            // ignore pure adds or deletes, they do not contribute to crossings
            if (entry.getLeftStatement().getBeginLine() != -1
                    && entry.getRightStatement().getBeginLine() != -1
                    && entry.getLeftStatement().getEndLine() != -1
                    && entry.getRightStatement().getEndLine() != -1) {
                array.add(entry);
            }
        }
        int size = array.size();
        for (int outerIndex = 0; outerIndex < size; outerIndex++) {
            for (int innerIndex = 0; innerIndex < size; innerIndex++) {
                if (innerIndex != outerIndex) {
                    if (outerIndex > innerIndex) {
                        if (array.get(outerIndex).getLeftStatement().getBeginLine() < array.get(innerIndex).getLeftStatement().getBeginLine()
                                || array.get(outerIndex).getRightStatement().getBeginLine() < array.get(innerIndex).getRightStatement().getBeginLine()) {
                            array.get(outerIndex).setCrossings(array.get(outerIndex).getCrossings() + 1);
                            array.get(innerIndex).setCrossings(array.get(innerIndex).getCrossings() + 1);
                        }
                    } else {
                        if (array.get(outerIndex).getLeftStatement().getBeginLine() > array.get(innerIndex).getLeftStatement().getBeginLine()
                                || array.get(outerIndex).getRightStatement().getBeginLine() > array.get(innerIndex).getRightStatement().getBeginLine()) {
                            array.get(outerIndex).setCrossings(array.get(outerIndex).getCrossings() + 1);
                            array.get(innerIndex).setCrossings(array.get(innerIndex).getCrossings() + 1);
                        }
                    }
                }
            }

        }
    }

    /**
     * Sets the STATUS of each StatementMappingEntry to Valid where
     * the Normalized Levenshtein Edit distance is <= 0.4.  The value
     * 0.4 comes from Canfora, et al., MSR 2007
     * <p>
     * To specify a value other than 0.4 use setValidMappingsCustomNEDThreshold()
     * To use the Normalized Token Levenshtein Edit distance instead use setValidMappingsCustomNTEDThreshold()
     * To use both edit distances in a weighted fashion use setValidMappingsCustomCombined()
     * @param resultSet the ResultSet to update
     */
    public void setValidMappings(StatementMappingResultSet resultSet) {

        // 0.4 comes from Canfora, et al, MSR 2007
        setValidMappingsCustomNEDThreshold(resultSet, canfora);
    }

    /**
     * Sets the STATUS of each StatementMappingEntry to Valid where
     * the Normalized Levenshtein Edit distance is <= normalizeEditDistanceThreshold. 
     * <p>
     * @param resultSet the ResultSet to update
     * @param normalizeEditDistanceThreshold the threshold to use
     */
    public void setValidMappingsCustomNEDThreshold(StatementMappingResultSet resultSet, double normalizeEditDistanceThreshold) {

        Iterator<StatementMappingEntry> iter = resultSet.iterator();
        while(iter.hasNext())
        {
            StatementMappingEntry entry = iter.next();
            if( runTokenEditDistance )
            {
                if(entry.getNormalizedTokenEditDistance() <= normalizeEditDistanceThreshold)
                {
                    entry.setStatus(StatementMappingStatus.VALID);
                }
            }
            else if ( this.useMinTokenOrTextEditDistance )
            {
                if(java.lang.Math.min( entry.getNormalizedTokenEditDistance(), 
                		entry.getNormalizedEditDistance()) <= normalizeEditDistanceThreshold)
                {
                    entry.setStatus(StatementMappingStatus.VALID);
                }            	
            }
            else
            {
            	if(entry.getNormalizedEditDistance() <= normalizeEditDistanceThreshold)
            	{
            		entry.setStatus(StatementMappingStatus.VALID);
            	}
            }
        }
    }

    /**
     * Sets the STATUS of each StatementMappingEntry to Valid where
     * the Normalized Token Levenshtein Edit distance is <= normalizeTokenEditDistanceThreshold. 
     * <p>
     * @param resultSet the ResultSet to update
     * @param normalizeTokenEditDistanceThreshold the threshold to use
     */
    public void setValidMappingsCustomNTEDThreshold(StatementMappingResultSet resultSet,double normalizeTokenEditDistanceThreshold) {

        Iterator<StatementMappingEntry> iter = resultSet.iterator();
        while(iter.hasNext())
        {
            StatementMappingEntry entry = iter.next();
            if(entry.getNormalizedTokenEditDistance() <= normalizeTokenEditDistanceThreshold)
            {
                entry.setStatus(StatementMappingStatus.VALID);
            }
        }
    }

    /**
     * Sets the STATUS of each StatementMappingEntry to Valid where
     * (Normalized Levenshtein Edit Distance *  normalizeEditDistanceWeight) + (Normalized Token Levenshtein Edit Distance *  (1-normalizeEditDistanceWeight)) <= threshold  
     * <p>
     * @param resultSet the ResultSet to update
     * @param normalizeEditDistanceWeight the weight to be applied to the Normalized Levenshtein Edit Distance 
     * @param threshold the threshold to use
     */    
    public void setValidMappingsCustomCombined(StatementMappingResultSet resultSet, double normalizeEditDistanceWeight, double threshold) {
        
        Iterator<StatementMappingEntry> iter = resultSet.iterator();
        while(iter.hasNext())
        {
            StatementMappingEntry entry = iter.next();
            if( (entry.getNormalizedEditDistance()*normalizeEditDistanceWeight ) +  (entry.getNormalizedTokenEditDistance()*(1-normalizeEditDistanceWeight)) <= threshold)
            {
                entry.setStatus(StatementMappingStatus.VALID);
            }
        }
    }

    /**
     * Sets the STATUS of each StatementMappingEntry to Probable where
     * it is currently Invalid but has a neighbor above and below that
     * are both marked as Valid.  These may not be immediate neighbors
     * but between the entry in question and the Valid neighbor there
     * must be no entries marked Possible or Probable and none of the
     * entries can be involved in any crossings.
     * <p>
     * Sets the STATUS of each StatementMappingEntry to Possible where
     * it is currently Invalid but has a neighbor above or below that
     * is marked as Valid and the other neighbor is not marked as Valid.
     * Same restrictions apply as above, but they only need to apply
     * in one direction.
     * <p>
     * Also, the neighbor must be part of the same method (if the
     * entry in question is in a method) or in the same class 
     * (if the entry in question is outside of a method, such as a
     * field declaration)
     * <p> 
     * @param resultSet the ResultSet to update
     */
    public void setProbablePossibleMappings(StatementMappingResultSet resultSet) {

        ArrayList<StatementMappingEntry> array = new ArrayList<StatementMappingEntry>();
        
        Iterator<StatementMappingEntry> iter = resultSet.iterator();
        while(iter.hasNext())
        {
            array.add(iter.next());
        }
        
        for(int index=0;index<array.size();index++)
        {
            
            StatementMappingEntry entry =array.get(index);
            
            if( entry.getStatus() == StatementMappingStatus.INVALID  && entry.getCrossings() == 0 && !entry.isPureAdd() && !entry.isPureDelete())
            {
                // try to turn it into PROBABLE or POSSIBLE

                // proabbleTop and probableBottom have three values
                // -1 : initial value, no decision yet
                //  0 : found a violation
                // >0 : found a Valid mapping, the value is the index into the array where the Valid entry was found
                int probableTop =-1, probableBottom =-1;
                
                MethodDeclarationStatement containingMethodEntry = getMethodContaining(entry.getLeftStatement(), leftFile);
                
                // check top
                for(int backIndex=index-1; backIndex>-1 && probableTop == -1 ; backIndex--)
                {
                    StatementMappingEntry candidateEntry = array.get(backIndex);
                    
                    MethodDeclarationStatement containingMethodCandidateEntry=null;
                        containingMethodCandidateEntry = getMethodContaining(candidateEntry.getLeftStatement(), leftFile);
                    
                    if( (containingMethodEntry == null ||  containingMethodEntry != containingMethodCandidateEntry) ) 
                    {
                        // we ran off the end of the method/class so the top is NOT a probable match
                        backIndex = -1;
                        probableTop = 0;
                    }
                    else
                    {
                        if (candidateEntry.getStatus() != StatementMappingStatus.VALID  && candidateEntry.getCrossings() > 0)
                        {
                            // we found something that is not VALID and has crossings
                            probableTop = 0;
                        }
                        else if( candidateEntry.getStatus() == StatementMappingStatus.VALID )
                        {
                            // success
                            probableTop = backIndex;
                        }
                    }
                    
                }
                
                // check bottom
                for(int bottomIndex=index+1; bottomIndex< array.size() && probableBottom == -1 ; bottomIndex++)
                {
                    StatementMappingEntry candidateEntry = array.get(bottomIndex);
                    
                    MethodDeclarationStatement containingMethodCandidateEntry=null;
                    containingMethodCandidateEntry = getMethodContaining(candidateEntry.getLeftStatement(), leftFile);
                    
                    if( (containingMethodEntry == null ||  containingMethodEntry != containingMethodCandidateEntry) ) 
                    {
                        // we ran off the end of the method/class so the top is NOT a probable match
                        bottomIndex = -1;
                        probableBottom = 0;
                    }
                    else
                    {
                        if (candidateEntry.getStatus() != StatementMappingStatus.VALID  && candidateEntry.getCrossings() > 0)
                        {
                            // we found something that is not VALID and has crossings
                            probableBottom = 0;
                        }
                        else if( candidateEntry.getStatus() == StatementMappingStatus.VALID )
                        {
                            // success
                            probableBottom = bottomIndex;
                        }
                    }
                }
                
                if (probableTop > 0 && probableBottom > 0)
                {
                    //set this as Probable
                    //set all (non-pure add/delete) between probableTop and probableBottom to Probable
                    for(int entryIndex=probableTop+1; entryIndex < probableBottom; entryIndex++)
                    {
                        StatementMappingEntry changeEntry = array.get(entryIndex);
                        if( !changeEntry.isPureAdd() && !changeEntry.isPureDelete() )
                        {
                            changeEntry.setStatus(StatementMappingStatus.PROBABLE);
                        }
                    }
                }
                else if(probableTop > 0)
                {
                    //set this as Possible
                    for(int entryIndex=probableTop+1; entryIndex <= index; entryIndex++)
                    {
                        StatementMappingEntry changeEntry = array.get(entryIndex);
                        if( !changeEntry.isPureAdd() && !changeEntry.isPureDelete() )
                        {
                            changeEntry.setStatus(StatementMappingStatus.POSSIBLE);
                        }
                    }                    
                }
                else if(probableBottom > 0)
                {
                    //set this as Possible
                    for(int entryIndex=index; entryIndex < probableBottom; entryIndex++)
                    {
                        StatementMappingEntry changeEntry = array.get(entryIndex);
                        if( !changeEntry.isPureAdd() && !changeEntry.isPureDelete() )
                        {
                            changeEntry.setStatus(StatementMappingStatus.POSSIBLE);
                        }
                    }                    
                    
                }
                
            }
        }
        
    }

    
    private MethodDeclarationStatement getMethodContaining(Statement stmt, SourceFile file)
    {
    	Statement parent = null;
    	
        if (stmt == null)
        {
        	return null;
        }
        parent = stmt.getParent();
        while(parent != null && !(parent instanceof MethodDeclarationStatement))
        {
        	parent = parent.getParent();
        }
        
        return (MethodDeclarationStatement)parent;
    }

    private ClassDeclaration getClassContaining(Statement stmt, ClassDeclaration classArg)
    {
        boolean found = false;
        ClassDeclaration classDecl = classArg;
        
        Iterator<ClassDeclaration> classIter = classArg.getInnerClasses().iterator();
        while(classIter.hasNext() && !found)
        {
            classDecl = classIter.next();
            
            //FIXME
//            if( classDecl.contains(stmt))
//            {
//                /* see if this is contained in an inner class */
//                classDecl = getClassContaining(stmt, classDecl);
//                found = true;
//            }
        }
        
        if (!found )
        {
        	//FIXME
//            if( classArg.contains(stmt))
//            {
//                classDecl = classArg;
//            }
//            else
//            {
//                classDecl = null;
//            }
        }
        return classDecl;
    }
    
    private ClassDeclaration getClassContaining(Statement stmt, SourceFile file)
    {
        boolean found = false;
        ClassDeclaration classDecl = null;
        
        Iterator<ClassSource> topLevelClassIter = file.getClassIterator();
        while(topLevelClassIter.hasNext() && !found)
        {
            ClassSource classSrc = topLevelClassIter.next();
            
            // FIXME
//            if( classSrc.getClassDecl().contains(stmt))
//            {
//                /* see if stmt is in an inner class */
//                classDecl = getClassContaining(stmt, classSrc.getClassDecl());
//                found = true;
//            }
        }
        
        if (! found )
        {
            classDecl = null;
        }
        return classDecl;
    }

	public boolean isUseMinTokenOrTextEditDistance() {
		return useMinTokenOrTextEditDistance;
	}

	public void setUseMinTokenOrTextEditDistance(boolean useMinTokenOrTextEditDistance) {
		this.useMinTokenOrTextEditDistance = useMinTokenOrTextEditDistance;
	}

    /**
     * @return the mETHOD_MAPPING_THRESHOLD
     */
    public double getMethodMappingThreshold()
    {
        return methodMappingThreshold;
    }

    /**
     * @param method_mapping_threshold the mETHOD_MAPPING_THRESHOLD to set
     */
    public void setMethodMappingThreshold(double method_mapping_threshold)
    {
        this.methodMappingThreshold = method_mapping_threshold;
    }

    /**
     * @return the fieldMappingThreshold
     */
    public double getFieldMappingThreshold()
    {
        return fieldMappingThreshold;
    }

    /**
     * @param field_mapping_threshold the fieldMappingThreshold to set
     */
    public void setFieldMappingThreshold(double field_mapping_threshold)
    {
        fieldMappingThreshold = field_mapping_threshold;
    }

    /**
     * @return the classMappingThreshold
     */
    public double getClassMappingThreshold()
    {
        return classMappingThreshold;
    }

    /**
     * @param class_mapping_threshold the classMappingThreshold to set
     */
    public void setClassMappingThreshold(double class_mapping_threshold)
    {
        classMappingThreshold = class_mapping_threshold;
    }

    /**
     * @return the canfora
     */
    public double getCanfora()
    {
        return canfora;
    }

    /**
     * @param canfora the canfora to set
     */
    public void setCanfora(double canfora)
    {
        this.canfora = canfora;
    }
}
